#include <lib3d/node/SymbolAtlasTextImpl.h>

#include <lib3d/node/text/SymbolAtlas.h>
#include <lib3d/node/text/AtlasMetricsHolder.h>
#include <lib3d/hard/VertexBufferBuilder2D.h>
#include <lib3d/node/TextRenderInfo.h>
#include <system/assert.h>

#include <TrueText/TextMetrics.h>
#include <TrueText/AtlasTextBuilder.h>
#include <TrueText/AtlasText.h>
#include <TrueText/TextBounds.h>

using namespace liba::lib3d::hard;

namespace liba {
namespace lib3d {
namespace node {

struct SymbolAtlasTextImplPrivates 
{
	SymbolAtlasTextImpl* This;
	text::SymbolAtlas* atlas;
	TrueText::AtlasTextBuilder atlasTextBuilder;
	bool atlasTextBuilderValid;
	bool hardwareValid;
	int primitiveCount;
	int vertexCount;

	void BuildAtlasTextBuilder();
	void BuildHardware(hard::VertexBuffer* vb);
};

SymbolAtlasTextImpl::SymbolAtlasTextImpl(text::SymbolAtlas* atlas)
	: privates(new SymbolAtlasTextImplPrivates())
{
	privates->This = this;
	privates->atlas = atlas;
	privates->atlasTextBuilderValid = false;
	privates->hardwareValid = false;
	privates->primitiveCount = 0;
	privates->vertexCount = 0;
	hard::Manager::add_dependent(this);
}

SymbolAtlasTextImpl::~SymbolAtlasTextImpl()
{ 
	hard::Manager::remove_dependent(this);
}

void SymbolAtlasTextImpl::on_add_hardware(hard::Manager::hardware_type* ha)
{
	hard_vector.push_back(SweetPair(ha, ha->create_vertex_buffer(true)));
}

void SymbolAtlasTextImpl::render_to_list(hard::Hardware * hardware, TextRenderInfo* tri, hard::RenderList * render_list)
{
	hard::VertexBuffer* vb = find_data(hardware);
	if (!privates->hardwareValid)
		privates->BuildHardware(vb);

	hard::RenderModel render_model;
	render_model.world = tri->total_matrix;
	render_model.world.rc(0, 3) += 0.5f;
	render_model.world.rc(1, 3) += 0.5f;
	
	render_model.vb = vb;
	render_model.primitive_start = 0;
	render_model.primitive_count = privates->primitiveCount;
	render_model.ib = 0;
	render_model.vertex_start = 0;
	render_model.vertex_count = privates->vertexCount;

	render_model.material.diffuse = tri->color;
	render_model.material.emissive = tri->color;
	tri->transparency = math::clamp( tri->transparency, 0.0001f, 0.9999f );
	render_model.material.transparency = tri->transparency;
	render_model.filter_texture = false;
	privates->atlas->render_to_list(hardware, &render_model.material.emissive_texture);

	render_list->models.push_back(render_model);
}

float SymbolAtlasTextImpl::get_text_width(const std::wstring & text)
{
	if (!privates->atlasTextBuilderValid)
		privates->BuildAtlasTextBuilder();
	
	return privates->atlasTextBuilder.GetBounds().GetDimensions().Width;
}

float SymbolAtlasTextImpl::get_text_height() 
{
	if (!privates->atlasTextBuilderValid)
		privates->BuildAtlasTextBuilder();
	
	return privates->atlasTextBuilder.GetBounds().GetDimensions().Height;
}

void SymbolAtlasTextImpl::text_data_changed()
{
	privates->atlasTextBuilderValid = false;
	privates->hardwareValid = false;
}

void SymbolAtlasTextImpl::change_valignment(VAlignment value)
{
	LIBA_FAIL("Not implemented yet");
}

TextImpl* SymbolAtlasTextImpl::clone()
{
	LIBA_FAIL("Not implemented yet");
	return new SymbolAtlasTextImpl(privates->atlas);
}

void SymbolAtlasTextImplPrivates::BuildAtlasTextBuilder()
{
	const TextData* data = This->GetTextData();
	boost::shared_ptr<TrueText::AtlasMetrics> am = this->atlas->GetAtlasMetrics().Value;
	this->atlasTextBuilder.SetAtlasMetrics(am);
	this->atlasTextBuilder.SetString(data->GetText().c_str());
	this->atlasTextBuilder.SetYAxisDirection(true);

	this->atlasTextBuilderValid = true;
}

void SymbolAtlasTextImplPrivates::BuildHardware(hard::VertexBuffer* vb)
{
	if (!this->atlasTextBuilderValid)
		this->BuildAtlasTextBuilder();

	VertexBufferBuilder2D vbb;
	vbb.SetZ(0.0f);
	vbb.SetTextureSize(this->atlas->GetWidth(), this->atlas->GetHeight());

	float xOffset = 0.0f;
	switch (This->GetTextData()->GetAlignment())
	{
	case TextData::CENTER:
		xOffset = -This->get_text_width(std::wstring()) / 2;
		break;
	case TextData::RIGHT:
		xOffset = -This->get_text_width(std::wstring());
	}

	const TrueText::AtlasText& at = this->atlasTextBuilder.GetAtlasText();

	for (std::vector<TrueText::AtlasTextEntry>::const_iterator i = at.Entries.begin(); i != at.Entries.end(); ++i)
	{
		int w = i->Area.right - i->Area.left + 1;
		int h = i->Area.bottom - i->Area.top + 1; 
		vbb.AddRect(
			math::Rect<float>(i->At.x + xOffset, i->At.y, w, h),
			math::Rect<int>(i->Area.left, i->Area.top, w, h));
	}

	this->vertexCount = at.Entries.size() * 6;
	this->primitiveCount = at.Entries.size() * 2;

	vbb.UpdateVertexBuffer(vb);
}

} // namespace node
} // namespace lib3d
} // namespace liba